﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using DependencyAnalysis;
using DependencyAnalysis.DataModel;
using DependencyAnalysis.Serialization;

using theeg.DependencyAnalysis.CommonAddin.Behaviors;
using MemberDescriptor = DependencyAnalysis.DataModel.MemberDescriptor;
using TypeDescriptor = DependencyAnalysis.DataModel.TypeDescriptor;

namespace theeg.DependencyAnalysis.CommonAddin.Models
{
    /// <summary>
    /// 
    /// </summary>
    public class AddinModel : INotifyPropertyChanged
    {
        private readonly ICommand _reloadCommand;
        public ICommand ReloadCommand { get { return _reloadCommand; } }
        private readonly ICommand _analyzeCommand;
        public ICommand AnalyzeCommand { get { return _analyzeCommand; } }
        private readonly ICommand _closeListCommand;
        public ICommand CloseListCommand
        {
            get { return _closeListCommand; }
        }

        public ISolution Solution { get; private set; }

        public bool AnalyzeInProcess { get; set; }

        public bool IsAnalyzing
        {
            get { return _isAnalyzing; }
            private set
            {
                if (Equals(value, _isAnalyzing)) return;
                _isAnalyzing = value;
                RaisePropertyChanged("IsAnalyzing");
            }
        }

        public IEnumerable<IProject> Projects
        {
            get { return _projects; }
            private set
            {
                if (Equals(value, _projects)) return;
                _projects = value;
                RaisePropertyChanged("Projects");
            }
        }

        public DataContractExpandoObject Metrics
        {
            get { return _metrics; }
            set
            {
                if (Equals(value, _metrics)) return;
                _metrics = value;
                RaisePropertyChanged("Metrics");
            }
        }

        public IDependencyModel DependencyModel
        {
            get { return _dependencyModel; }
            set
            {
                if (Equals(value, _dependencyModel)) return;
                _dependencyModel = value;
                _proximityModel.DependencyModel = value;
                _architectureGraphModel.DependencyModel = value;
                _commonTestsModel.Model = value;
                _adjacencyMatrixModel.CalculateFromModel(value);
                RaisePropertyChanged("DependencyModel");
            }
        }

        private readonly ProximityModel _proximityModel = new ProximityModel();
        public ProximityModel ProximityModel
        {
            get { return _proximityModel; }
        }

        private readonly CommonTestsModel _commonTestsModel = new CommonTestsModel();
        public CommonTestsModel CommonTestsModel
        {
            get { return _commonTestsModel; }
        }

        private readonly ArchitectureGraphModel _architectureGraphModel = new ArchitectureGraphModel();
        public ArchitectureGraphModel ArchitectureGraphModel
        {
            get { return _architectureGraphModel; }
        }

        private readonly AdjacencyMatrixModel _adjacencyMatrixModel = new AdjacencyMatrixModel();
        public AdjacencyMatrixModel AdjacencyMatrixModel
        {
            get { return _adjacencyMatrixModel; }
        }

        private bool _isAnalyzing;
        private IEnumerable<IProject> _projects;
        private DataContractExpandoObject _metrics;
        private IDependencyModel _dependencyModel;

        public AddinModel(ISolution solution)
        {
            Solution = solution;
            RefreshProjects();
            Preferences.Load(Solution, Projects);
            _reloadCommand = new RelayCommand(x =>
                {
                    RefreshProjects();
                    Preferences.Load(Solution, Projects);
                });
            _analyzeCommand = new RelayCommand(x =>
                {
                    Preferences.Store(Solution, Projects);
                    AnalyzeSolution();
                });
            _closeListCommand = new RelayCommand(x => Dependencies = null);
            _proximityModel.FindDependencies = FindDependencies;
            _commonTestsModel.Load();
        }

        private void RefreshProjects()
        {
            Solution.Refresh();
            Projects = Solution.Projects;
        }

        public void AnalyzeSolution()
        {
            IsAnalyzing = true;
            var solution = Solution;
            solution.BuildFinished = BuildFinished;
            Action action = solution.Build;
            action.BeginInvoke(null, null);
        }

        private void BuildFinished()
        {
            var modules = new List<string>();
            foreach (var project in Projects)
            {
                if (!project.IsSelected) continue;
                var assemblyPath = project.OutputFileName;
                if (string.IsNullOrWhiteSpace(assemblyPath)) continue;
                modules.Add(assemblyPath);
            }

            Action action = () => StartAnalysis(modules.ToArray(), false, "DependencyAnalysisDomain_Generic");
            action.BeginInvoke(null, null);
        }

        //TODO elaborate on isTest flag
        public void StartAnalysis(string[] modules, bool isTest, string pipeName)
        {
            if (AnalyzeInProcess)
            {
                var setup = Setup.BuildStandardSetup();
                setup.StartAnalysis(modules);
                var model = setup.Model;
                var metricsReport = new MetricsReport { Setup = setup };
                var metrics = (DataContractExpandoObject)metricsReport.FetchReport<DataContractExpandoObject>();

                Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                {
                    DependencyModel = model;
                    Metrics = metrics;
                }));
            }
            else
            {
                var analysisHost = new Process
                {
                    StartInfo = new ProcessStartInfo(typeof(AnalysisHost.App).Assembly.Location)
                    {
                        UseShellExecute = false,
                        Arguments = pipeName
                    }
                };
                using (var pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.InOut))
                {
                    if (!isTest)
                    {
                        analysisHost.Start();
                    }
                    pipeServer.WaitForConnection();
                    try
                    {
                        var writer = new StreamWriter(pipeServer);
                        var reader = new StreamReader(pipeServer);

                        writer.AutoFlush = true;
                        writer.WriteLine(AnalysisHost.App.Synchronization);
                        pipeServer.WaitForPipeDrain();
                        foreach (var module in modules)
                        {
                            writer.WriteLine(module);
                        }
                        pipeServer.WaitForPipeDrain();
                        writer.WriteLine(AnalysisHost.App.Synchronization);
                        pipeServer.WaitForPipeDrain();

                        AnalysisHost.App.WaitForSynchronization(reader, () => !isTest && analysisHost.HasExited);
                        var result = (AnalysisHost.AnalysisResult)AnalysisHost.App.DeserializeFromStream(pipeServer);

                        result.DependencyModel.InstructionCache.Deserialize();
                        var setup = Setup.BuildSetupFromModel(result.DependencyModel);
                        var metricsReport = new MetricsReport { Setup = setup };
                        var metrics = (DataContractExpandoObject)metricsReport.FetchReport<DataContractExpandoObject>();

                        Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                        {
                            DependencyModel = result.DependencyModel;
                            Metrics = metrics;
                        }));
                    }
                    catch (IOException)
                    {
                    }
                }
                if (!isTest)
                {
                    analysisHost.WaitForExit();
                }
            }
            IsAnalyzing = false;
        }

        public void SelectCommandExecuted(object parameter)
        {

        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        private IEnumerable<DependencyDescriptor> _dependencies;
        public IEnumerable<DependencyDescriptor> Dependencies
        {
            get { return _dependencies; }
            set
            {
                if (Equals(_dependencies, value)) return;
                _dependencies = value;
                RaisePropertyChanged("Dependencies");
            }
        }

        private void FindDependencies(TransitionDescriptor transition)
        {
            if (transition == null)
            {
                Dependencies = null;
                return;
            }
            IDictionary<IDescriptor, ISet<DependencyDescriptor>> dependencyStructure;
            Func<DependencyDescriptor, bool> qualifier;
            if (transition.Source is ModuleDescriptor)
            {
                dependencyStructure = DependencyModel.DependenciesPerModuleOut;
                qualifier = x => Equals(x.Target.ImplementingType.Submodule.Module, transition.Target);
            }
            else if (transition.Source is SubmoduleDescriptor)
            {
                dependencyStructure = DependencyModel.DependenciesPerSubmoduleOut;
                qualifier = x => Equals(x.Target.ImplementingType.Submodule, transition.Target);
            }
            else if (transition.Source is TypeDescriptor)
            {
                dependencyStructure = DependencyModel.DependenciesPerTypeOut;
                qualifier = x => Equals(x.Target.ImplementingType, transition.Target);
            }
            else if (transition.Source is MemberDescriptor)
            {
                dependencyStructure = DependencyModel.DependenciesPerMemberOut;
                qualifier = x => Equals(x.Target, transition.Target);
            }
            else
            {
                return;
            }
            if (!dependencyStructure.ContainsKey(transition.Source))
            {
                Dependencies = new DependencyDescriptor[0];
                return;
            }
            Dependencies = (from x in dependencyStructure[transition.Source]
                            where qualifier(x)
                            select x).ToArray();
        }
    }
}
